#pragma once

#include <atlmisc.h>


enum EHideState
{
	hsAlwaysShow,	
	hsHidden,
	hsHidding,
	hsShowed,
	hsShowing
};

enum EHideDirection
{
	hdNone = 0,
	hdLeft = 1,
	hdTop = 2,
	hdRight = 4,
	hdBottom = 8,
	hdLeftTop = hdLeft | hdTop,
	hdRightTop = hdRight | hdTop,
	hdLeftBottom = hdLeft | hdBottom,
	hdRightBottom = hdRight | hdBottom
}	;

#define HIDDEN_EDGE 2
#define HIDDEN_STEPS 5


template <class T, UINT uiTID>
class CAutoHideImpl
{
public:
	CAutoHideImpl(): uiAssignedTimerID(uiTID), uiTimerID(0), bAutoHide(false),
		ehsHideState(hsAlwaysShow), ehdDirection(hdNone)
	{

	}

	~CAutoHideImpl()
	{

	}

	void EnableAutoHide(bool b = true){bAutoHide = b;}
	bool IsAutoHide(void){return bAutoHide;}

	BEGIN_MSG_MAP(CAutoHide)
		MESSAGE_HANDLER(WM_TIMER, OnTimer)
		MESSAGE_HANDLER(WM_SIZE, OnSize)
	END_MSG_MAP()

	LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
	{
		if(wParam == uiAssignedTimerID)
		{
			T * pT = static_cast<T *>(this);
			if(!bAutoHide)
			{
				pT->KillTimer(uiTimerID);
				uiTimerID = 0;
				HandleSetAutoHide(false);
			}
			else
				AutoHide();
		}
		return 0;
	}

	LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
	{
		if(wParam == SIZE_RESTORED && bAutoHide)
		{
			switch(ehsHideState)
			{
				case hsAlwaysShow:
					break;
				case hsHidden:
					ehsHideState = hsHidding;
					break;
				case hsHidding:

					break;
				case hsShowed:
					ehsHideState = hsShowing;
					break;
				case hsShowing:

					break;
			}
		}
		return 0;
	}

	void HandleSetAutoHide(bool bSetTo)
	{
		if(bAutoHide == bSetTo)
			return;
		//		T * pT = static_cast<T *>(this);
		bAutoHide = bSetTo;
		T * pT = static_cast<T *>(this);
		if(bAutoHide)	// from disable to enable
		{
			if(uiTimerID == 0)
				uiTimerID = pT->SetTimer(uiAssignedTimerID, 100);
			ehsHideState = hsAlwaysShow;
			ehdDirection = hdNone;
		}
		else	// from enable to disable
		{
			if(uiTimerID != 0)
			{
				pT->KillTimer(uiTimerID);
				uiTimerID = 0;
				ForceShow();
				ehsHideState = hsAlwaysShow;
				ehdDirection = hdNone;
			}
		}
	}

	void ForceShow(void)
	{
		T * pT = static_cast<T *>(this);
		if(!pT->IsWindowVisible())
			return;
		switch(ehsHideState)
		{
			case hsAlwaysShow:
			case hsShowed:
				break;
			case hsHidden:
			case hsHidding:
			case hsShowing:
				CRect rect;
				pT->GetWindowRect(&rect);
				int nsw = ::GetSystemMetrics(SM_CXSCREEN);
				int nsh = ::GetSystemMetrics(SM_CYSCREEN);
				int nDiffX = 0, nDiffY = 0;				
				if(rect.right > nsw)
					nDiffX = nsw - rect.right;
				if(rect.left < 0)
					nDiffX = -rect.left;
				if(rect.bottom > nsh)
					nDiffY = nsh - rect.bottom;
				if(rect.top < 0)
					nDiffY = -rect.top;
				rect.OffsetRect(nDiffX, nDiffY);
				pT->MoveWindow(&rect);
		}
	}


	void PrepareAutoHide(bool b)
	{
		EnableAutoHide(b);
		if(bAutoHide)
		{
			T * pT = static_cast<T *>(this);
			uiTimerID = pT->SetTimer(uiAssignedTimerID, 50);
		}
	}
protected:
	UINT uiAssignedTimerID;
	UINT uiTimerID;
	bool bAutoHide;
	EHideState ehsHideState;
	EHideDirection ehdDirection;

	void AutoHide(void)
	{
		T * pT = static_cast<T *>(this);
		if(!pT->IsWindowVisible())
			return;
		ehsHideState = UpdateAutoHideState(ehsHideState);
		switch(ehsHideState)
		{
		case hsAlwaysShow:

			break;
		case hsHidden:

			break;
		case hsHidding:
			DoHidding();
			break;
		case hsShowed:

			break;
		case hsShowing:
			DoShowing();
			break;
		}
	}


	void DoHidding(void)
	{
		CRect r;
		T * pT = static_cast<T *>(this);

		pT->GetWindowRect(&r);
		int nsw = ::GetSystemMetrics(SM_CXSCREEN);
		int nsh = ::GetSystemMetrics(SM_CYSCREEN);
		int nStepX = r.Width() / HIDDEN_STEPS;
		int nStepY = r.Height() / HIDDEN_STEPS;
		int nDeltaX = 0, nDeltaY = 0;
		bool bH = false, bV = false;

		switch(ehdDirection)
		{
		case hdLeft:
			nDeltaX = - nStepX;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.right < 2)
			{
				r.OffsetRect(2 - r.right, 0);
				bH = true;
			}
			bV = true;
			break;
		case hdTop:
			nDeltaY = -nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.bottom < 2)
			{
				r.OffsetRect(0, 2 - r.bottom);
				bV = true;
			}
			bH = true;
			break;
		case hdRight:
			nDeltaX = nStepX;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(nsw - r.left < 2)
			{
				r.OffsetRect(nsw - 2 - r.left, 0);
				bH = true;
			}
			bV = true;
			break;
		case hdBottom:
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(nsh - r.top < 2)
			{
				r.OffsetRect(0, nsh - 2 - r.top);
				bV = true;
			}
			bH = true;
			break;
		case hdLeftTop:
			nDeltaX = -nStepX;
			nDeltaY = -nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.right < 2)
			{
				r.OffsetRect(2 - r.right, 0);
				bH = true;
			}
			if(r.bottom < 2)
			{
				r.OffsetRect(0, 2 - r.bottom);
				bV = true;
			}
			break;
		case hdRightTop:
			nDeltaX = nStepX;
			nDeltaY = -nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(nsw - r.left < 2)
			{
				r.OffsetRect(nsw - 2 - r.left, 0);
				bH = true;
			}
			if(r.bottom < 2)
			{
				r.OffsetRect(0, 2 - r.bottom);
				bV = true;
			}
			break;
		case hdLeftBottom:
			nDeltaX = -nStepX;
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.right < 2)
			{
				r.OffsetRect(2 - r.right, 0);
				bH = true;
			}
			if(nsh - r.top < 2)
			{
				r.OffsetRect(0, r.top - nsh);
				bV = true;
			}
			break;
		case hdRightBottom:
			nDeltaX = nStepX;
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(nsw - r.right < 2)
			{
				r.OffsetRect(r.right - nsw, 0);
				bH = true;
			}
			if(nsh - r.top < 2)
			{
				r.OffsetRect(0, r.top - nsh);
				bV = true;
			}
			break;
		}
		BOOL b = pT->MoveWindow(&r);
		if(bH && bV)
			ehsHideState = hsHidden;
	}


	void DoShowing(void)
	{
		CRect r;
		T * pT = static_cast<T *>(this);

		pT->GetWindowRect(&r);
		int nsw = ::GetSystemMetrics(SM_CXSCREEN);
		int nsh = ::GetSystemMetrics(SM_CYSCREEN);
		int nStepX = r.Width() / HIDDEN_STEPS;
		int nStepY = r.Height() / HIDDEN_STEPS;
		int nDeltaX = 0, nDeltaY = 0;
		bool bH = false, bV = false;

		switch(ehdDirection)
		{
		case hdLeft:
			nDeltaX = nStepX;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.left > 0)
			{
				r.OffsetRect(-r.left, 0);
				bH = true;
			}
			bV = true;
			break;
		case hdTop:
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.top > 0)
			{
				r.OffsetRect(0, -r.top);
				bV = true;
			}
			bH = true;
			break;
		case hdRight:
			nDeltaX = -nStepX;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.right < nsw)
			{
				r.OffsetRect(nsw - r.right, 0);
				bH = true;
			}
			bV = true;
			break;
		case hdBottom:
			nDeltaY = -nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.bottom < nsh)
			{
				r.OffsetRect(0, nsh - r.bottom);
				bV = true;
			}
			bH = true;
			break;
		case hdLeftTop:
			nDeltaX = nStepX;
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.left > 0)
			{
				r.OffsetRect(-r.left, 0);
				bH = true;
			}
			if(r.top > 0)
			{
				r.OffsetRect(0, -r.top);
				bV = true;
			}
			break;
		case hdRightTop:
			nDeltaX = -nStepX;
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.right < nsw)
			{
				r.OffsetRect(nsw - r.right, 0);
				bH = true;
			}
			if(r.top > 0)
			{
				r.OffsetRect(0, -r.top);
				bV = true;
			}
			break;
		case hdLeftBottom:
			nDeltaX = -nStepX;
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.left > 0)
			{
				r.OffsetRect(-r.left, 0);
				bH = true;
			}
			if(r.bottom < nsh)
			{
				r.OffsetRect(0, nsh - r.top);
				bV = true;
			}
			break;
		case hdRightBottom:
			nDeltaX = nStepX;
			nDeltaY = nStepY;
			r.OffsetRect(nDeltaX, nDeltaY);
			if(r.right < nsw)
			{
				r.OffsetRect(nsw - r.right, 0);
				bH = true;
			}
			if(r.bottom < nsh)
			{
				r.OffsetRect(0, nsh - r.top);
				bV = true;
			}
			break;
		}
		pT->MoveWindow(&r);
		if(bH && bV)
			ehsHideState = hsShowed;
	}


	EHideState UpdateAutoHideState(EHideState hs)
	{
		if(bAutoHide)
		{
			CRect r;
			CPoint pt;
			bool bCursorInWindow;
			EHideDirection hd;
			T * pT = static_cast<T *>(this);

			pT->GetWindowRect(&r);
			if(r.Width() < 2 && r.Height() < 2)
				return hsAlwaysShow;
			int nsw = ::GetSystemMetrics(SM_CXSCREEN);
			int nsh = ::GetSystemMetrics(SM_CYSCREEN);
			::GetCursorPos(&pt);
			bCursorInWindow = (r.PtInRect(pt) != 0);

			if(r.left > 0 && r.right < nsw && r.top > 0 && r.bottom < nsh)
			{
				ehdDirection = hdNone;
				return hsAlwaysShow;
			}
			switch(hs)
			{
			case hsAlwaysShow:
				hd = FindHideDirection(r, nsw, nsh);
				ehdDirection = hd;
				if(hd == hdNone)
					return hsAlwaysShow;
				else
				{
					if(bCursorInWindow)
						return hsShowed;
					else
						return hsHidding;
				}
				break;
			case hsHidden:
				if(bCursorInWindow)
					return hsShowing;
				else
					return hsHidden;
				break;
			case hsHidding:
				if(bCursorInWindow)
					return hsShowing;
				else
					return hsHidding;
				break;
			case hsShowed:
				if(bCursorInWindow)
					return hsShowed;
				else
					return hsHidding;
				break;
			case hsShowing:
				if(bCursorInWindow)
					return hsShowing;
				else 
					return hsHidding;
				break;
			}
		}
		return hsAlwaysShow;
	}

	EHideDirection FindHideDirection(CRect & rtWin, int nsw, int nsh)
	{
		EHideDirection hd = hdNone;
		if(rtWin.top == 0)
		{
			hd = hdTop;
			if(rtWin.left == 0)
				hd = hdLeftTop;
			else
			{
				if(rtWin.right == nsw)
					hd = hdRightTop;
			}
			return hd;
		}
		if(rtWin.bottom == nsh)
		{
			hd = hdBottom;
			if(rtWin.left == 0)
				hd = hdLeftBottom;
			else
			{
				if(rtWin.right == nsw)
					hd = hdRightBottom;
			}
			return hd;
		}
		int n = hd;
		if(rtWin.left == 0)
		{
			if(!(n & hdRight))
				hd = (EHideDirection)(n | hdLeft);
		}
		if(rtWin.right == nsw)
		{
			if(!(n & hdLeft))
				hd = (EHideDirection)(n | hdRight);
		}
		return hd;
	}
};
